﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using TimersXP.Messaging;
using TimersXP.Helpers;

#if !SL3
using System.Linq.Expressions;
#endif

#if NETFX_CORE
using Windows.ApplicationModel;
#else
using System.Windows;
#endif

namespace TimersXP.ViewModels
{
    /// <summary>A base class for the ViewModel classes.</summary>
    ///   [ClassInfo(typeof(ViewModelBase),
    ////  VersionString = "4.2.15",
    ////  DateString = "201309262235",
    ////  Description = "A base class for the ViewModel classes in the MVVM pattern.",
    ////  UrlContacts = "http://www.galasoft.ch/contact_en.html",
    ////  Email = "laurent@galasoft.ch")]
    [SuppressMessage("Microsoft.Design", "CA1012", Justification = "Constructors should remain public to allow serialization.")]
    public abstract class ViewModelBase : ObservableObject, ICleanup
    {
        #region Class Variables
        /// <summary>Class Boolean Variable that is a nullable value tells if the ViewModel implementing ViewModelBase is in design mode or not.</summary>
        private static bool? isInDesignMode;
        /// <summary>The messenger instance</summary>
        private IMessenger messengerInstance;
        #endregion Class Variables

        #region Constructors
        /// <summary>Initializes a new instance of the <see cref="ViewModelBase"/> class.</summary>
        /// <NOTE>// " : this(null)" calls the other constructor and passes null. Constructor overloading</NOTE>
        protected ViewModelBase() : this(null) { }

        /// <summary>Initializes a new instance of the <see cref="ViewModelBase"/> class.</summary>
        /// <param name="messenger">An instance of a <see cref="Messenger"/> used to broadcast messages to other objects.
        /// If null, this class will attempt to broadcast using the Messenger's default instance.</param>
        protected ViewModelBase(IMessenger messenger) { MessengerInstance = messenger; }
        #endregion Constructors

        #region Events
        /// <summary>Occurs when [property changed].</summary>
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion Events

        #region Properties
        /// <summary>
        /// Gets a value indicating whether [is in design mode static] is in-fact in design mode (running in Blend or Visual Studio)
        /// false to indicate that it is not in design mode but in run-time mode.
        /// </summary>
        /// <value><c>true</c> if [is in design mode]; otherwise, <c>false</c>.</value>
        /// <NOTE>
        /// SuppressMessage is used by System.Diagnostics.CodeAnalysis. When you are applying code analysis to your project you will sometimes
        /// get warnings that you want to ignore. You do that by using SuprpressMessageAttribute. Now code analysis is built into the "higher" SKU's
        /// of Visual Studio but previously you had to use the tool named FxCop to prerform the analysis. Using code analysis on your code will not only
        /// increase the quality of your code but you might also learn a few things in the process.
        /// http://stackoverflow.com/questions/11910764/whats-the-use-of-supressmessage-attribute
        /// </NOTE>
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMemberAsStatic", Justification = "Non static member needed for data binding")]
        public bool IsInDesignMode { get { return IsInDesignModeStatic; } }

        /// <summary>
        /// Gets a value indicating whether [is in design mode static] is in-fact in design mode (running in Blend or Visual Studio)
        /// false to indicate that it is not in design mode but in run-time mode.
        /// </summary>
        /// <value>c>true</c> if [is in design mode static]; otherwise, <c>false</c>.</value>
        /// <NOTE>
        /// SuppressMessage is used by System.Diagnostics.CodeAnalysis. When you are applying code analysis to your project you will sometimes
        /// get warnings that you want to ignore. You do that by using SuprpressMessageAttribute. Now code analysis is built into the "higher" SKU's
        /// of Visual Studio but previously you had to use the tool named FxCop to prerform the analysis. Using code analysis on your code will not only
        /// increase the quality of your code but you might also learn a few things in the process.
        /// http://stackoverflow.com/questions/11910764/whats-the-use-of-supressmessage-attribute
        /// </NOTE>
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "The security risk here is neglectible.")]
        public static bool IsInDesignModeStatic
        {
            get
            {
                if (!isInDesignMode.HasValue)
                {
#if SILVERLIGHT
                    isInDesignMode = DesignerProperties.IsInDesignTool;
#else
                    var prop = DesignerProperties.IsInDesignModeProperty;
                    isInDesignMode =
                        (bool)DependencyPropertyDescriptor.FromProperty(prop, typeof(FrameworkElement)).Metadata.DefaultValue;

                    // Just to be sure
                    if (!isInDesignMode.Value && Process.GetCurrentProcess().ProcessName.StartsWith("devenv", StringComparison.Ordinal))
                        isInDesignMode = true;
#endif
                }
                return isInDesignMode.Value;
            }
        }

        /// <summary>
        /// Gets or sets an instance of a <see cref="IMessenger"/> used to broadcast messages to other objects.
        /// if null, then class will attempt to broadcast using the Messenger's default instance.
        /// </summary>
        /// <value>The messenger instance.</value>
        protected IMessenger MessengerInstance 
        {
            get { return messengerInstance ?? Messenger.Default; }
            set { messengerInstance = value; }
        }
        #endregion Properties

        #region Functions
        #region Public Virtual Functions
        /// <summary>Unregisteres this instance from the Messenger class. <para>To cleanup additional resources, override this method, clean up and then call base.Cleanup().</para></summary>
        public virtual void Cleanup() { MessengerInstance.Unregister(this); }
        #endregion Public Virtual Functions

        #region Protected Functions
        #region Protected Virtual Functions
        /// <summary>Broadcasts a PropertyChangedMessage using either the instance of the Messenger that was passed to this class (if available) or the Messenger's default instance.</summary>
        /// <typeparam name="T">The type of the property that changed.</typeparam>
        /// <param name="oldValue">The value of the property before it changed.</param>
        /// <param name="newValue">The value of the property after it changed.</param>
        /// <param name="propertyName">Name of the property that changed.</param>
        protected virtual void Broadcast<T>(T oldValue, T newValue, string propertyName)
        {
            var message = new PropertyChangedMessage<T>(this, oldValue, newValue, propertyName);
            MessengerInstance.Send(message);
        }

        /// <summary>
        /// Raises the PropertyChanged event if needed, and broadcasts a PropertyChangedMessage using the Messenger instance
        /// (or the static default instance if no Messenger instance is available).
        /// </summary>
        /// <typeparam name="T">The type of the property that changed.</typeparam>
        /// <param name="propertyName">The name of the property that changed.</param>
        /// <param name="oldValue">The property's value before the change occurred.</param>
        /// <param name="newValue">The property's value after the change occurred.</param>
        /// <param name="broadcast">If set to <c>true</c>, a PropertyChangedMessage will be [broadcast]. If <c>false</c>, only the event will be raised.</param>
        /// <remarks>If the propertyName parameter does not correspond to an existing property on the current class, an exception is thrown in DEBUG configuration only.</remarks>
        /// <exception cref="System.ArgumentException">This method cannot be called with an empty string;propertyName</exception>
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed"),
        SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This cannot be an event")]
        protected virtual void RaisePropertyChanged<T>(
#if CMNATTR
            [CallerMemberName] string propertyName = null,
#else
            string propertyName,
#endif
            T oldValue = default(T),
            T newValue = default(T),
            bool broadcast = false)
        {
            if (string.IsNullOrEmpty(propertyName))
                throw new ArgumentException("This method cannot be called with an empty string", "propertyName");

            RaisePropertyChanged(propertyName);
            if (broadcast)
                Broadcast(oldValue, newValue, propertyName);
        }

#if !SL3
        /// <summary>
        /// Raises the PropertyChanged event if needed, and broadcasts a PropertyChangedMessage using the Messenger instance
        /// (or the static default instance if no Messenger instance is available).
        /// </summary>
        /// <typeparam name="T">The type of the property that changed.</typeparam>
        /// <param name="propertyExpression">An expression identifying the property that changed.</param>
        /// <param name="oldValue">The property's old value before the change occured.</param>
        /// <param name="newValue">The property's new value after the change occured.</param>
        /// <param name="broadcast">If set to <c>true</c>, a PropertyChangedMessage will be [broadcast]. If false, only the event will be raised.</param>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This cannot be an event")]
        [SuppressMessage("Microsoft.Design", "CA1006:GenericMethodsShouldProvideTypeParameter", Justification = "This syntax is more convenient than the alternatives.")]
        protected virtual void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression, T oldValue, T newValue, bool broadcast)
        {
            var handler = PropertyChangedHandler;
            if (handler != null || broadcast)
            {
                var propertyName = GetPropertyName(propertyExpression);
                if (handler != null)
                    handler(this, new PropertyChangedEventArgs(propertyName));

                if (broadcast)
                    Broadcast(oldValue, newValue, propertyName);
            }
        }
#endif
        #endregion Protected Virtual Functions
//Continued from above in the Protected Virtual Functions region of code!
#if !SL3
        /// <summary>
        /// Assigns a new value to the property. Then, raises the PropertyChanged event if needed, and broadcasts a PropertyChangedMessage using the Messenger instance
        /// (or the static default instance if no Messenger instance is available)
        /// </summary>
        /// <typeparam name="T">The type of the property that changed.</typeparam>
        /// <param name="propertyExpression">An expression identifying the property that changed.</param>
        /// <param name="field">The field storing the property's value.</param>
        /// <param name="newValue">The property's new value after the change occurred.</param>
        /// <param name="broadcast">If set to <c>true</c>, a PropertyChangedMessage will be [broadcast]. If false, only the event will be raised.</param>
        /// <returns>True if the PropertyChanged event was raised, false otherwise.</returns>
        [SuppressMessage("Microsoft.Design", "CA1006DoNotNestGenericTypesInMemberSignatures", Justification = "This syntax is more convenient than the alternatives."),
        SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "1#")]
        protected bool Set<T>(Expression<Func<T>> propertyExpression, ref T field, T newValue, bool broadcast)
        {
            if (EqualityComparer<T>.Default.Equals(field, newValue))
                return false;

            RaisePropertyChanging(propertyExpression);
            var oldValue = field;
            field = newValue;
            RaisePropertyChanged(propertyExpression, oldValue, field, broadcast);
            return true;
        }

        /// <summary>
        /// Assigns a new value to the property. Then raises the PropertyChanged event if needed, and broadcasts a PropertyChangedMessage using the Messenger instance
        /// (or the static default instance if no Messenger instance is available).
        /// </summary>
        /// <typeparam name="T">The type of the property that changed.</typeparam>
        /// <param name="propertyName">The name of the property that changed.</param>
        /// <param name="field">The field storing the property's value.</param>
        /// <param name="newValue">The property's new value after the change occurred.</param>
        /// <param name="broadcast">If set to <c>true</c>, a PropertyChangedMessage will be [broadcast]. If false, only the event will be raised.</param>
        /// <returns>True if the PropertyChanged event was raised, false otherwise.</returns>
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed"),
        SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "1#")]
        protected bool Set<T>(string propertyName, ref T field, T newValue = default(T), bool broadcast = false)
        {
            if (EqualityComparer<T>.Default.Equals(field, newValue))
                return false;

            RaisePropertyChanging(propertyName);
            var oldValue = field;
            field = newValue;
            RaisePropertyChanged(propertyName, oldValue, field, broadcast);
            return true;
        }
#endif

#if CMNATTR
        /// <summary>
        /// Assigns a new value to the property. Then raises the PropertyChanged event if needed, and broadcasts a PropertyChangedMessage using the Messenger instance
        /// (or the static default instance if no Messenger instance is available).
        /// </summary>
        /// <typeparam name="T">The type of the property that changed.</typeparam>
        /// <param name="field">The field storing the property's value.</param>
        /// <param name="newValue">The property's new value after the change occurred.</param>
        /// <param name="broadcast">If set to <c>true</c>, a PropertyChangedMessage will be [broadcast]. If false, only the event will be raised.</param>
        /// <param name="propertyName">(optional) The name of the property that changed.</param>
        /// <returns>True if the PropertyChanged event was raised, false otherwise.</returns>
        protected bool Set<T>(ref T field, T newValue = default(T), bool broadcast = false, [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, newValue))
                return false;

            RaisePropertyChanging(propertyName);
            var oldValue = field;
            field = newValue;
            RaisePropertyChanged(propertyName, oldValue, field, broadcast);
            return true;
        }
#endif
        #endregion Protected Functions
        #endregion Functions
    }
}
